<?php
/**
 * 文档生成器
 *
 * @author modongxiao
 * @time 2019-08-26
 * @version 1.3
 */

// 声明命名空间
namespace common\components\document;


/* 示例 */
/*
action 使用
class DocumentController extends \yii\base\Controller
{
    public function actionIndex()
    {
        $read_status = false; // 默认不允许读取
        // 2.0 系统
        //if (YII_DEBUG == false || PRODUCT_ENV == false) {
        //    // 调试或者非线上环境
        //    $read_status = true;
        //}
        // 3.0 系统
        if (YII_ENV != 'prod') {
            // 非线上环境打开
            $read_status = true;
        }
        $document = new \app\common\components\document\DocumentComponent();
        $document->document([
            // 单个目录
            //'dir' => __DIR__,
            // 多个目录
            'dir' => [
                __DIR__
            ],
            'title' => '狸米基础微信平台',
        ], $read_status);
    }
}

componets 组件结构（文件位置）
    componets/document/DocumentComponent.php
    componets/document/document.php
保证两个文件在同一目录下
*/

/*
version  2.0- 则代表该接口使用小于或等于2.0版本  无 “-”则代表所有版本
returnjson josn 数据  returnstring 正常字符数据  可以写多条
*/

/**
 * @url  /document
 * @method GET
 * @name 作业 - 作业批改报告
 * @author 莫东肖
 * @version 1.0
 * @param int work_id R| 作业id
 * @param string openid R| 微信openid
 * @param int student_id O| 学生id
 * @param int timestamp O| 13位时间戳
 * @response string code 消息代码
 * @response string desc 消息说明
 * @response json data 返回数据格式
 * @field int coin 金币数量
 * @field int work_id 作业id
 * @field int work_type 1.直播作业，2.综合提高
 * @field date assign_date 开放时间  示例：2018-05-28
 * @field int grade   年级id 示例：8
 * @field string accuracy 正确率 示例 80%
 * @field int show_answer 是否展示答案解析 1 展示 0 不展示
 * @returnjson {"code":1000,"desc":"成功","data":{"taskid":1127,"coin":1,"sum":6652}}
 * @returnstring
 */


class DocumentComponent
{
    // 版本
    private $version = [];
    // 不生成文档的文件
    private $remove = [
        // "BaseController.php",
        // "DocumentController.php"
    ];
    // 页面展示的数据
    private $data = [];
    // 解析的文件
    private $file = [];

    /**
     * 文档列表：文档生成器
     * @param str $data ['dir'] 生成目录的文档位置
     * @param str $data ['title']  标题
     * @param bool $read_status 查看状态，自定义当前环境打开或关闭
     */
    public function document($data = [], $read_status = true)
    {
        // 判断是否为测试环境
        if ($read_status == false) {
            echo '此接口暂不支持线上请求';
            exit;
        }

        // 版本
        parse_str($_SERVER['QUERY_STRING'], $query_string);
        $input['version'] = isset($query_string['version']) ? $query_string['version'] : '1.0'; // Request $request
        // 目录下文件列表,判断是单个路径或多个路径
        if (is_array($data['dir'])) {
            foreach ($data['dir'] as $value) {
                $this->file($value);
            }
        } else {
            $this->file($data['dir']);
        }
        // 数据解析
        $this->data();
        if (!$this->data) {
            echo '未检索到文件中存在版本与接口信息！';
            exit;
        }
        // 数据展示处理，版本与标题
        $array = $this->method([
            'version' => $input['version']
        ]);
        // 数据为空或者数据错误时
        if (empty($input['version']) || !in_array($input['version'], $array['version'])) {
            $input['version'] = $array['version'][0];
        }

        /* 页面需要的变量 */
        $title = $data['title'];
        $data = $this->data;
        $array = $array;
        $version = $input['version'];
        /* 加载模板 */
        $file = __DIR__ . '/document.php';
        if (is_file($file)) {
            include $file;
        } else {
            echo '未找到文档模板文件';
        }
        exit;
    }

    // 文档文件
    private function file($dir)
    {
        $dir = rtrim($dir, '/') . '/';
        $data = scandir($dir);
        foreach ($data as $key => $value) {
            if ($value != '.' && $value != '..' && !in_array($value, $this->remove)) {
                $this->file[] = $dir . $value;
            }
        }
    }

    // 文件解析
    private function data()
    {
        foreach ($this->file as $value) {
            $file = file_get_contents($value);
            $this->block($file);
        }
    }

    // 文件区块匹配
    private function block($file)
    {
        preg_match_all("/\/\*\*(.*?)\*\//is", $file, $html);
        if ($html[1]) {
            foreach ($html[1] as $value) {
                $this->analyze($value); // 区块解析
            }
        }
    }

    // 区块解析
    private function analyze($html)
    {
        // 参数初始化
        $data = [
            'url' => '', // 接口信息
            'version' => '1.0', // 默认版本
            'method' => '', // 方法
            'title' => '其它', // 标题
            'name' => '', // 接口名
            'author' => '', // 作者
            'param' => [], // 请求参数
            'response' => [], // 返回信息
            'field' => [], // 返回参数
            'returnjson' => [], // 返回示例 json
            'returnstring' => [] // 返回示例字符串
        ];

        // 建立临时文件，有时候会出现用 PHP_EOL 或者 \r\n 无法判断出换行的异常问题
        $temp = tmpfile();
        fwrite($temp, $html);
        fseek($temp, 0); // 在文件指针中定位
        $array = [];
        if ($temp) {
            while (($buffer = fgets($temp)) !== false) {
                $buffer = trim($buffer); // 去除两边空格
                if ($buffer) {
                    $array[] = $buffer;
                }
            }
            if (!feof($temp)) { // 测试文件指针是否到了文件结束的位置
                // echo "Error: unexpected fgets() fail\n";
            }
            fclose($temp);
        }

        if (empty($array)) {
            return false;
        }

        foreach ($array as $v) {
            // url
            if (preg_match("/\*\s@url\s+(.*)/is", $v, $temp)) {
                $data['url'] = trim($temp[1]);
            }
            // version
            if (preg_match("/\*\s@version\s+(.*)/is", $v, $temp)) {
                $data['version'] = trim($temp[1]);
            }
            // method
            if (preg_match("/\*\s@method\s+(.*)/is", $v, $temp)) {
                $data['method'] = trim($temp[1]);
            }
            // name
            if (preg_match("/\*\s@name\s+(.*)/is", $v, $temp)) {
                $data['name'] = trim($temp[1]);
                // title 标题，中文和英文标点
                if (preg_match("/(.*)[|~-~-]+(.*)/is", $temp[1], $temp)) {
                    $data['title'] = trim($temp[1]);
                    $data['name'] = trim($temp[2]);
                }
            }
            // author
            if (preg_match("/\*\s@author\s+(.*)/is", $v, $temp)) {
                $data['author'] = trim($temp[1]);
            }
            // param
            if (preg_match("/\*\s@param\s+(.*)/is", $v, $temp)) {
                $temp = $this->analyze_param(trim($temp[1]));
                if ($temp) {
                    $data['param'][] = $temp;
                }
            }
            // response
            if (preg_match("/\*\s@response\s+(.*)/is", $v, $temp)) {
                $temp = $this->analyze_response_field(trim($temp[1]));
                if ($temp) {
                    $data['response'][] = $temp;
                }
            }
            // field
            if (preg_match("/\*\s@field\s+(.*)/is", $v, $temp)) {
                $temp = $this->analyze_response_field(trim($temp[1]));  // 与 response 格式相同
                if ($temp) {
                    $data['field'][] = $temp;
                }
            }
            // returnjson
            if (preg_match("/\*\s@returnjson\s+(.*)/is", $v, $temp)) {
                $temp = $this->analyze_returnjson(trim($temp[1]));  // json 格式化
                if ($temp) {
                    $data['returnjson'][] = $temp;
                }
            }
            // returnstring
            if (preg_match("/\*\s@returnstring\s+(.*)/is", $v, $temp)) {
                $temp = trim($temp[1]);
                if ($temp) {
                    $data['returnstring'][] = $temp;
                }
            }
        }

        // 必备参数
        if ($data['url'] && $data['method'] && $data['name']) {
            $this->data[] = $data;
        }
    }

    // 请求参数解析
    private function analyze_param($data)
    {
        $array = [
            'type' => '',// 类型
            'param' => '', // 参数
            'pass' => 'R',// 是否必传
            'bewrite' => '', // 描述
        ];

        // 必传
        if (strpos($data, 'R|')) {
            $temp = explode('R|', $data);
            $array['pass'] = 'R';
        }
        // 非必传
        if (strpos($data, 'O|')) {
            $temp = explode('O|', $data);
            $array['pass'] = 'O';
        }
        if (empty($temp)) { // 未匹配到字符串
            return false;
        }
        // 切割前后字符串处理
        $array['bewrite'] = $temp[1];

        $temp = trim(preg_replace("/\s(?=\s)/", "\\1", $temp[0]));  // 多个空格合并为一个
        $temp = explode(" ", $temp);
        if (count($temp) != 2) {
            return false;
        }
        $array['type'] = $temp[0];
        $array['param'] = $temp[1];

        return $array;
    }

    // 返回信息解析
    private function analyze_response_field($data)
    {
        $array = [
            'type' => '',// 类型
            'param' => '', // 参数
            'bewrite' => '', // 描述
        ];
        $temp = trim(preg_replace("/\s(?=\s)/", "\\1", $data));  // 多个空格合并为一个
        $temp = explode(" ", $temp);
        if (count($temp) < 3) { // 参数数量不正确
            return false;
        }
        $array['type'] = $temp[0]; // type 赋值
        $array['param'] = $temp[1]; // param 赋值

        $strpos = strpos($data, $temp[1]) + mb_strlen($temp[1], 'UTF-8'); // 第二个参数最后一次出现的位置
        $array['bewrite'] = trim(mb_substr($data, $strpos)); // 备注赋值

        return $array;
    }

    /* Json数据格式化
    * @param  Mixed  $data   数据
    * @param  String $indent 缩进字符，默认4个空格
    * @return JSON
    */
    private function analyze_returnjson($data, $indent = null)
    {
        // 是否为正常的json格式
        json_decode($data);
        if (json_last_error() != 0) {
            return false;
        }

        // json encode
        // $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        // 将urlencode的内容进行urldecode
        $data = urldecode($data);
        // 缩进处理
        $ret = '';
        $pos = 0;
        $length = strlen($data);
        $indent = isset($indent) ? $indent : '    ';
        $newline = "\n";
        $prevchar = '';
        $outofquotes = true;
        for ($i = 0; $i <= $length; $i++) {
            $char = substr($data, $i, 1);
            if ($char == '"' && $prevchar != '\\') {
                $outofquotes = !$outofquotes;
            } elseif (($char == '}' || $char == ']') && $outofquotes) {
                $ret .= $newline;
                $pos--;
                for ($j = 0; $j < $pos; $j++) {
                    $ret .= $indent;
                }
            }
            $ret .= $char;
            if (($char == ',' || $char == '{' || $char == '[') && $outofquotes) {
                $ret .= $newline;
                if ($char == '{' || $char == '[') {
                    $pos++;
                }
                for ($j = 0; $j < $pos; $j++) {
                    $ret .= $indent;
                }
            }
            $prevchar = $char;
        }
        return $ret;
    }

    // 数据展示处理，版本与标题
    public function method($input)
    {
        // 初始化
        $data = $title = $version = [];
        $pattern = '/^([\d.]+)(.*)/is';
        // 获取版本列表，标题列表
        foreach ($this->data as $key => $value) {
            if (preg_match($pattern, $value['version'], $temp)) {
                if (!$temp[2]) { // 版本列表
                    $version[$value['version']] = [];
                }
            }
        }
        $version = array_keys($version); // 版本列表
        sort($version, SORT_NUMERIC);
        $version = array_reverse($version); // 排序
        // 判断输入版本号，强制符合要求
        if (!in_array($input['version'], $version)) {
            $input['version'] = max($version);
        }

        // 格式版本数据，某版本下所有参数
        foreach ($this->data as $key => $value) {
            if (preg_match($pattern, $value['version'], $temp)) {
                if ($temp[2]) { // 版本废弃接口
                    $value['version'] = $temp[1]; // 处理版本号
                    if ($value['version'] < $input['version']) {
                        continue; // 跳过低版本废弃接口
                    }
                } else {
                    // 跳过高于当前版本
                    if ($value['version'] > $input['version']) {
                        continue;
                    }
                }
                $title[$value['title']] = []; // 标题列表
                $value['version'] = $input['version']; // 统一为当前输入的版本号
                $data[] = $value; // 处理符合版本的数据
            }
        }
        $this->data = $data;
        $title = array_keys($title); // 标题列表
        $version = array_slice($version, 0, 5); // 只取前5个

        return [
            'version' => $version,
            'title' => $title
        ];
    }
}
